#include "memory.h"
#include "uarch/sim.h"

extern Simulator Sim;

#define PCM_SETS (Sim.settings.pcm_sets)
#define PCM_ASSOC (Sim.settings.pcm_assoc)
#define DRAM_SETS (Sim.settings.dram_sets)
#define DRAM_ASSOC (Sim.settings.dram_assoc)
#define DRAM_LINE_SETS (Sim.settings.dram_line_sets)
#define DRAM_LINE_ASSOC (Sim.settings.dram_line_assoc)
#define PCM_LINES (Sim.settings.pcm_lines)
#define MEM_LINE_SIZE (Sim.settings.mem_line_size)

#define DRAM_HIT_TIME (Sim.settings.dram_hit_time)
#define PCM_RB_HIT_TIME (Sim.settings.pcm_rb_hit_time)
#define PCM_HIT_TIME  (Sim.settings.pcm_hit_time)
#define HDD_HIT_TIME  (Sim.settings.hdd_hit_time)

Memory_controller::Memory_controller(ModuleID moduleID, int hit_time)
	: Module (moduleID, "MC_")
{
	prev_addr[0] = 0;
	prev_addr[1] = 0;
	current_addr = 0;

	this->hit_time = hit_time;

	int i,j;

	pcm_tag = (tag_t **) malloc ((1<<PCM_SETS) * sizeof (tag_t *));
	for (i = 0; i < (1<<PCM_SETS); i++)
	{
		pcm_tag[i] = (tag_t *) malloc ((1<<PCM_ASSOC) * sizeof (tag_t));
		for (j=0; j < (1<<PCM_ASSOC); j++)
		{
			pcm_tag[i][j].tag = 0;
			pcm_tag[i][j].valid = 0;
		}
	}

	dram_tag = (tag_t **) malloc ((1<<DRAM_SETS) * sizeof (tag_t *));
	for (i = 0; i < (1<<DRAM_SETS); i++)
	{
		dram_tag[i] = (tag_t *) malloc ((1<<DRAM_ASSOC) * sizeof (tag_t));
		for (j=0; j < (1<<DRAM_ASSOC); j++)
		{
			dram_tag[i][j].tag = 0;
			dram_tag[i][j].valid = 0;
		}
	}

	dram_line_tag = (tag_t **) malloc ((1<<DRAM_LINE_SETS) * sizeof (tag_t *));
	for (i = 0; i < (1<<DRAM_LINE_SETS); i++)
	{
		dram_line_tag[i] = (tag_t *) malloc ((1<<DRAM_LINE_ASSOC) * sizeof (tag_t));
		for (j=0; j < (1<<DRAM_LINE_ASSOC); j++)
		{
			dram_line_tag[i][j].tag = 0;
			dram_line_tag[i][j].valid = 0;
		}
	}

	pcm_cache = (fa_tag_t *) malloc (PCM_LINES * sizeof (fa_tag_t));
	for (i=0; i < (signed int) PCM_LINES; i++)
	{
		pcm_cache[i].lru = 0;
		pcm_cache[i].tag = 0;
		pcm_cache[i].valid = 0;
	}

    this->stats = new Memory_controller_stat_engine (this);
}

Memory_controller::~Memory_controller()
{
	int i;

	for (i = 0; i < 1<<PCM_SETS; i++)
    	free (pcm_tag[i]);
	free (pcm_tag);

	for (i = 0; i < 1<<DRAM_SETS; i++)
    	free (dram_tag[i]);
	free (dram_tag);

	for (i = 0; i < 1<<DRAM_LINE_SETS; i++)
    	free (dram_line_tag[i]);
	free (dram_line_tag);

	free (pcm_cache);

    delete stats;
}

inline void Memory_controller::preprocess_request (Mreq *request)
{
    if (TICK_TOCK_DEBUG || ADDR_DEBUG)
        request->print_msg (moduleID, "Memory controller tick/tock");

    stats->off_chip_accesses++;
}

void Memory_controller::tick()
{
    Mreq *request;
    Mreq *reply;
    timestamp_t latency;

    /** No 'client check' needed in memory controller because it always has permission
        to execute the 'Directory side' protocol behavior.  */
    while ((request = read_input_port ()) != NULL)
    {
        preprocess_request (request);

		switch (request->msg) {
        //TODO: Requests from memory go through L3 then L2 then original requestor.. :(

		case SUPPLY_DATA_S:
            /** These are supply messages for when the top lvl is a tag structure, not a cache. Therefore requests that have enough permissions still need to be supplied from mem*/
            stats->off_chip_SUPPLY_S_accesses++;
            reply = new Mreq (C_DATA_S, request->addr, request->preq, moduleID, request->fwd_mid);
            latency = process_request (request->addr);
            write_output_port (reply, Global_Clock + latency);
            stats->read_latency_hist.collect (latency);
            break;

        case SUPPLY_DATA_E:
            /** These are supply messages for when the top lvl is a tag structure, not a cache. Therefore requests that have enough permissions still need to be supplied from mem*/
            stats->off_chip_SUPPLY_E_accesses++;
            reply = new Mreq (C_DATA_E, request->addr, request->preq, moduleID, request->fwd_mid);
            latency = process_request (request->addr);
            write_output_port (reply, Global_Clock + latency);
            stats->read_latency_hist.collect (latency);
            break;

        case SUPPLY_DATA_M:
            /** These are supply messages for when the top lvl is a tag structure, not a cache. Therefore requests that have enough permissions still need to be supplied from mem*/
            stats->off_chip_SUPPLY_M_accesses++;
            reply = new Mreq (C_DATA_M, request->addr, request->preq, moduleID, request->fwd_mid);
            latency = process_request (request->addr);
            write_output_port (reply, Global_Clock + latency);
            stats->read_latency_hist.collect (latency);
            break;


        case C_GETIS:
            /** Despite being GETIS, 'convert' this into GETIE because client-dir mapping for memory
                protocol is one-to-one; noone else can possibly have it except the top tier who is asking.*/
            stats->off_chip_GETIS_accesses++;
            reply = new Mreq (C_DATA_E, request->addr, request->preq, moduleID, request->src_mid);
            latency = process_request (request->addr);
            write_output_port (reply, Global_Clock + latency);
            stats->read_latency_hist.collect (latency);
            break;

        case C_GETIM:
            stats->off_chip_GETIM_accesses++;
            reply = new Mreq (C_DATA_M, request->addr, request->preq, moduleID, request->src_mid);
            latency = process_request(request->addr);
            write_output_port (reply, Global_Clock + latency);
            stats->read_latency_hist.collect (latency);
            break;

        case C_PUTM:
            /** Writeback. MSI and MESI are blocking protocols that require an unblock message.  */
            stats->off_chip_PUTM_accesses++;
            reply = new Mreq (D_WB_ACK, request->addr, request->preq, moduleID, request->src_mid);
            latency = process_request(request->addr);
fprintf(stderr, "Need to fix the PUTM latency!!!\n");
            write_output_port (reply, Global_Clock + 1);
            break;

        default:
            request->print_msg (moduleID, "ERROR");
            fatal_error ("Memory_controller tick: illegal message type!\n");
        }
        request->add_to_history (request);
    }
}

void Memory_controller::tock()
{
//    fatal_error ("Memory controller tock should never be called!\n");
}

void Memory_controller::update_address_history(unsigned long long addr)
{
	prev_addr[1] = prev_addr[0];
	prev_addr[0] = current_addr;

	current_addr = addr;
}

timestamp_t Memory_controller::process_request (unsigned long long addr)
{
	unsigned long long int old_addr, flag;
	unsigned long long int i;
	unsigned long long int dl_flag, pr_flag, pcm_flag, hd_flag;
	timestamp_t access_latency;

	unsigned long long dram_pl_tag, dram_pl_set, dram_pl_assoc;
	unsigned long long dram_ll_tag, dram_ll_set, dram_ll_assoc;
	unsigned long long pcm_pl_tag, pcm_pl_set, pcm_pl_assoc;
	unsigned long long pcm_rb_tag;

	if (Sim.settings.mem_model_enabled == false)
	        return hit_time;

	update_address_history(addr);


	/* Mask all tag / set / assoc combinations for
	 * DRAM Page Level : Communication between DRAM and HDD
	 * DRAM Line Level : Communication between DRAM and PCM
	 * PCM  Page Level : Communication from PCM to (DRAM or HDD)
	 * PCM  Row Buffer Tag : Used for PCM to DRAM transfers
	 */

	dram_pl_tag = get_dram_page_level_tag(current_addr);
	dram_pl_set = get_dram_page_level_set_index(current_addr);
	dram_pl_assoc = get_dram_page_level_assoc_index(current_addr);

	dram_ll_tag = get_dram_line_level_tag(current_addr);
	dram_ll_set = get_dram_line_level_set_index(current_addr);
	dram_ll_assoc = get_dram_line_level_assoc_index(current_addr);

	pcm_pl_tag = get_pcm_page_level_tag(current_addr);
	pcm_pl_set = get_pcm_page_level_set_index(current_addr);
	pcm_pl_assoc = get_pcm_page_level_assoc_index(current_addr);

	pcm_rb_tag = get_pcm_row_buffer_tag(current_addr);

	/* End Bit Masking Routine */
	
	flag = 1;
	dl_flag = 1;
	pr_flag = 1;
	pcm_flag = 1;
	hd_flag = 1;

	/* DRAM Memory Tag Bit Checking */

	if(dram_tag[dram_pl_set][dram_pl_assoc].tag==dram_pl_tag
			&& dram_tag[dram_pl_set][dram_pl_assoc].valid==1)
	{
		access_latency = DRAM_HIT_TIME;
		stats->dmcounter++;
		dl_flag = 0;
		pr_flag = 0;
		pcm_flag = 0;
		hd_flag = 0;
	}

	/* DRAM Tag Checking Ends */

	/* DRAM Line Check */

	if(dram_line_tag[dram_ll_set][dram_ll_assoc].tag==dram_ll_tag
			&& dram_line_tag[dram_ll_set][dram_ll_assoc].valid==1 && dl_flag==1)
	{
		access_latency = DRAM_HIT_TIME;
		stats->dlcounter++;
		pr_flag = 0;
		pcm_flag = 0;
		hd_flag = 0;
	}

	/* DRAM Line Checking Ends */

	/* PCM Row Buffer bit checking */

	if(pr_flag==1)
	{
		if (Sim.settings.memory_baseline == true)
		{
			if (pcm_cache[0].tag==pcm_pl_tag && pcm_cache[i].valid==1)
			{
				access_latency = PCM_RB_HIT_TIME;

				pcm_flag = 0;
				hd_flag = 0;

				dram_line_tag[dram_ll_set][dram_ll_assoc].tag = dram_ll_tag;
				dram_line_tag[dram_ll_set][dram_ll_assoc].valid = 1;
				stats->plcounter++;
			}
		}
		else
		{
			for(i=0;i<PCM_LINES;i++)
			{
				if(pcm_cache[i].tag==pcm_rb_tag && pcm_cache[i].valid==1)
				{
					access_latency = PCM_RB_HIT_TIME;
					//counter++;
					pcm_cache[i].lru++;
					i = PCM_LINES;

					pcm_flag = 0;
					hd_flag = 0;

					dram_line_tag[dram_ll_set][dram_ll_assoc].tag = dram_ll_tag;
					dram_line_tag[dram_ll_set][dram_ll_assoc].valid = 1;
					stats->plcounter++;
				}
			}
		}
	}

	/* PCM Row Buffer Checking Ends */

	/* PCM Memory tag bit checking */

	if(pcm_tag[pcm_pl_set][pcm_pl_assoc].tag==pcm_pl_tag
			&& pcm_tag[pcm_pl_set][pcm_pl_assoc].valid==1
			&& pcm_flag==1)
	{
		unsigned long long lru_index;

		access_latency = PCM_HIT_TIME;

		/* LRU Replacement of PCM Row Buffer */

		if(Sim.settings.memory_baseline == true)
		{
			pcm_cache[0].tag = pcm_pl_tag;
			pcm_cache[0].valid = 1;
		}
		else
		{
			lru_index = get_pcm_rb_lru_index();

			pcm_cache[lru_index].tag = pcm_rb_tag;
			pcm_cache[lru_index].valid = 1;
			pcm_cache[lru_index].lru = 1;
		}
		stats->pmcounter++;

		flag = 1;

		/* Concatenating bits together to reconstruct address -- Need to generalize this into a function */
		old_addr = (((~(ALT_MASK<<DRAM_ASSOC))&dram_pl_assoc)<<PAGE_SIZE_OS)|(((~(ALT_MASK<<DRAM_SETS))&dram_pl_set)<<(DRAM_ASSOC+PAGE_SIZE_OS))|((dram_tag[dram_pl_set][dram_pl_assoc].tag)<<(DRAM_ASSOC+DRAM_SETS+PAGE_SIZE_OS));

		unsigned long long old_dram_pl_tag, old_dram_pl_set, old_dram_pl_assoc;

		old_dram_pl_tag = get_dram_page_level_tag(old_addr);
		old_dram_pl_set = get_dram_page_level_set_index(old_addr);
		old_dram_pl_assoc = get_dram_page_level_assoc_index(old_addr);

		if(dram_tag[old_dram_pl_set][old_dram_pl_assoc].tag==1)
		{

			dram_tag[old_dram_pl_set][old_dram_pl_assoc].tag = 0;

			/*---- Restore page to PCM -------*/

			unsigned long long old_pcm_pl_tag, old_pcm_pl_set, old_pcm_pl_assoc;

			old_pcm_pl_tag = get_pcm_page_level_tag(old_addr);
			old_pcm_pl_set = get_pcm_page_level_set_index(old_addr);
			old_pcm_pl_assoc = get_pcm_page_level_assoc_index(old_addr);

			pcm_tag[old_pcm_pl_set][old_pcm_pl_assoc].tag = old_pcm_pl_tag;
			pcm_tag[old_pcm_pl_set][old_pcm_pl_assoc].valid = 1;
			flag = 0;

		}

		old_addr = (((~(ALT_MASK<<DRAM_LINE_ASSOC))&dram_ll_assoc)<<MEM_LINE_SIZE)|(((~(ALT_MASK<<DRAM_LINE_SETS))&dram_ll_set)<<(DRAM_LINE_SETS+PAGE_SIZE_OS))|((dram_line_tag[dram_ll_set][dram_ll_assoc].tag)<<(DRAM_LINE_SETS+DRAM_LINE_ASSOC+MEM_LINE_SIZE));

		unsigned long long int old_dram_ll_tag, old_dram_ll_set, old_dram_ll_assoc;

		old_dram_ll_tag = (old_addr>>(MEM_LINE_SIZE + DRAM_LINE_ASSOC + DRAM_LINE_SETS))&(MASK>>(MEM_LINE_SIZE + DRAM_LINE_SETS + DRAM_LINE_ASSOC));
		old_dram_ll_set = (~(ALT_MASK<<DRAM_LINE_SETS))&(old_addr>>(DRAM_LINE_ASSOC + MEM_LINE_SIZE));
		old_dram_ll_assoc = (~(ALT_MASK<<DRAM_LINE_ASSOC))&(old_addr>>MEM_LINE_SIZE);

		if(dram_line_tag[old_dram_ll_set][old_dram_ll_assoc].tag==1)
		{
			/*-------Restore Line to PCM cache--------*/
			
			unsigned long long int lru_index, old_pcm_rb_tag;				
			
			if (Sim.settings.memory_baseline == true)
			{
				pcm_cache[0].tag = get_pcm_page_level_tag(old_addr);
				pcm_cache[0].valid = 1;
			}
			else
			{
				lru_index = get_pcm_rb_lru_index();
				old_pcm_rb_tag = get_pcm_row_buffer_tag(old_addr);

				pcm_cache[lru_index].tag = old_pcm_rb_tag;
				pcm_cache[lru_index].valid = 1;
				pcm_cache[lru_index].lru = 1;
			}
        }
		
		dram_line_tag[dram_ll_set][dram_ll_assoc].tag = dram_ll_tag;
		dram_line_tag[dram_ll_set][dram_ll_assoc].valid = 1;

		hd_flag=0;
	}

	if(hd_flag==1)
	{
		pcm_tag[pcm_pl_set][pcm_pl_assoc].tag = pcm_pl_tag;
		pcm_tag[pcm_pl_set][pcm_pl_assoc].valid = 1;

		access_latency = HDD_HIT_TIME;

		/* Get Tag Bits and Restore to PCM */ 

		if(dram_tag[dram_pl_set][dram_pl_assoc].valid==1)
		{

			unsigned long long old_pcm_pl_tag, old_pcm_pl_set, old_pcm_pl_assoc;

			/* Concatenation of bits... needs to be generalized */
			old_addr = (((~(ALT_MASK<<DRAM_ASSOC))&dram_pl_assoc)<<PAGE_SIZE_OS)|(((~(ALT_MASK<<DRAM_SETS))&dram_pl_set)<<(DRAM_ASSOC+PAGE_SIZE_OS))|((dram_tag[dram_pl_set][dram_pl_assoc].tag)<<(DRAM_SETS+DRAM_ASSOC+PAGE_SIZE_OS));

			old_pcm_pl_tag = get_pcm_page_level_tag(old_addr);
			old_pcm_pl_set = get_pcm_page_level_set_index(old_addr);
			old_pcm_pl_assoc = get_pcm_page_level_assoc_index(old_addr);

			pcm_tag[old_pcm_pl_set][old_pcm_pl_assoc].tag = old_pcm_pl_tag;
			pcm_tag[old_pcm_pl_set][old_pcm_pl_assoc].valid = 1;

		}
		
		dram_tag[dram_pl_set][dram_pl_assoc].tag = dram_pl_tag;
		dram_tag[dram_pl_set][dram_pl_assoc].valid = 1;
		stats->hdcounter++;
	}

	stats->total_latency += access_latency;

	return access_latency;
}

void Memory_controller::run_trace ()
{
	unsigned long long k;


//	addr = ADDR_SEED;

	current_addr = START_ADDR;
	
	for(k=0;k<TRACE_LENGTH;k++)
	{
		current_addr = generate_addr(current_addr, k);

		process_request(current_addr);

		//printf("access_latency: %lf\ttotal_latency: %lf\n",access_latency,total_latency);
        //printf("%llx\n",current_addr);
	}
	stats->print_stats(cerr);

}

unsigned long long Memory_controller::generate_addr (unsigned long long old_addr, unsigned long long access_num)
{
	unsigned long long addr_new;
	unsigned long long k = access_num;

	addr_new = old_addr;

	if(k%73==0 && k!=0)
	{
		addr_new = addr_new + prev_addr[0];
	}
	else if(k%67==0 & k!=0)
	{
		addr_new = addr_new + prev_addr[1];
	}
	else if(k%57==0 & k!=0)
	{
		addr_new = addr_new + RAND_SEED1;
	}
	else
	{
		addr_new = addr_new + 0x00ef00000;
	}

	addr_new = addr_new & 0x0000000fffffffff;

	return addr_new;
}

unsigned long long Memory_controller::get_pcm_rb_lru_index()
{
	unsigned long long temp5, m, i;
	temp5 = pcm_cache[0].lru;
	m = 0;
	for(i=0;i<PCM_LINES;i++)
	{
		if(temp5>pcm_cache[i].lru)
		{
				temp5 = pcm_cache[i].lru;
				m = i;
		}
	}
	return m;
}

void Memory_controller::print_config (FILE * file)
{
    fprintf (file, "Memory Controller Setings\n");
    fprintf (file, "PCM_SETS %d ", 1<<PCM_SETS);
    fprintf (file, "PCM_ASSOC %d ", 1<<PCM_ASSOC);
    fprintf (file, "DRAM_SETS %d ", 1<<DRAM_SETS);
    fprintf (file, "DRAM_ASSOC %d ", 1<<DRAM_ASSOC);
    fprintf (file, "DRAM_LINE_SETS %d ", 1<<DRAM_LINE_SETS);
    fprintf (file, "DRAM_LINE_ASSOC %d ", 1<<DRAM_LINE_ASSOC);
    fprintf (file, "PCM_LINES %d ", PCM_LINES);
    fprintf (file, "LINE_SIZE %d",  1<<MEM_LINE_SIZE);
    fprintf (file, "\n");
}

inline unsigned long long Memory_controller::get_dram_page_level_tag(unsigned long long current_addr)
{
	return (current_addr>>(PAGE_SIZE_OS + DRAM_ASSOC + DRAM_SETS))&(MASK>>(PAGE_SIZE_OS + DRAM_ASSOC + DRAM_SETS));
}

inline unsigned long long Memory_controller::get_dram_page_level_set_index(unsigned long long current_addr)
{
	/* Set bits for DRAM Page level mapping */
	return (~(ALT_MASK<<DRAM_SETS))&(current_addr>>(DRAM_ASSOC + PAGE_SIZE_OS));
}

inline unsigned long long Memory_controller::get_dram_page_level_assoc_index(unsigned long long current_addr)
{
	/* Assoc bits for DRAM Page level mapping */
	return (~(ALT_MASK<<DRAM_ASSOC))&(current_addr>>PAGE_SIZE_OS);
}

inline unsigned long long Memory_controller::get_dram_line_level_tag(unsigned long long current_addr)
{
	/* Tag bits for DRAM line level mapping (communication with pcm) */
	return (current_addr>>(MEM_LINE_SIZE + DRAM_LINE_ASSOC + DRAM_LINE_SETS))&(MASK>>(MEM_LINE_SIZE + DRAM_LINE_ASSOC + DRAM_LINE_SETS));
}

inline unsigned long long Memory_controller::get_dram_line_level_set_index(unsigned long long current_addr)
{
	/* Set bits for DRAM line level mapping */
	return (~(ALT_MASK<<DRAM_LINE_SETS))&(current_addr>>(DRAM_LINE_ASSOC + MEM_LINE_SIZE));
}

inline unsigned long long Memory_controller::get_dram_line_level_assoc_index(unsigned long long current_addr)
{
	/* Assoc bits for DRAM line level mapping */
	return (~(ALT_MASK<<DRAM_LINE_ASSOC))&(current_addr>>MEM_LINE_SIZE);
}

inline unsigned long long Memory_controller::get_pcm_row_buffer_tag(unsigned long long current_addr)
{
	/* PCM Row Buffer Cache Tag bits */
	return (current_addr>>MEM_LINE_SIZE)&(MASK>>MEM_LINE_SIZE);
}

inline unsigned long long Memory_controller::get_pcm_page_level_tag(unsigned long long current_addr)
{
	/* Tag bits for PCM page level mapping (receiving evicted pages from dram) */
	return (current_addr>>(PAGE_SIZE_OS + PCM_ASSOC + PCM_SETS))&(MASK>>(PAGE_SIZE_OS + PCM_ASSOC + PCM_SETS));
}

inline unsigned long long Memory_controller::get_pcm_page_level_set_index(unsigned long long current_addr)
{
	/* Set bits for PCM page level mapping */
	return (~(ALT_MASK<<PCM_SETS))&(current_addr>>(PCM_ASSOC + PAGE_SIZE_OS));
}

inline unsigned long long Memory_controller::get_pcm_page_level_assoc_index(unsigned long long current_addr)
{
	/* Assoc bits for PCM page level mapping */
	return (~(ALT_MASK<<PCM_ASSOC))&(current_addr>>PAGE_SIZE_OS);
}

Memory_controller_stat_engine::Memory_controller_stat_engine (Module *m)
    : Stat_engine (m),
      dmcounter ("DRAM Page Level Hits",""),
      dlcounter ("DRAM Line Level Hits",""),
      pmcounter ("PCM Page Level Hits",""),
      plcounter ("PCM Line Level Hits",""),
      hdcounter ("Hard Drive Accesses",""),
      total_latency ("Total Latency",""),
      off_chip_accesses ("Off-chip Accesses", ""),
      off_chip_SUPPLY_S_accesses ("SUPPLY_DATA_S Off-chip Accesses", ""),
      off_chip_SUPPLY_E_accesses ("SUPPLY_DATA_E Off-chip Accesses", ""),
      off_chip_SUPPLY_M_accesses ("SUPPLY_DATA_M Off-chip Accesses", ""),
      off_chip_GETIS_accesses ("GETIS Off-chip Accesses", ""),
      off_chip_GETIM_accesses ("GETIM Off-chip Accesses", ""),
      off_chip_PUTM_accesses ("PUTM Off-chip Accesses", ""),
      read_latency_hist ("Read access latency histogram", "", 350, 2, 0)
{
}

Memory_controller_stat_engine::Memory_controller_stat_engine ()
    : Stat_engine (NULL),
      dmcounter ("DRAM Page Level Hits",""),
      dlcounter ("DRAM Line Level Hits",""),
      pmcounter ("PCM Page Level Hits",""),
      plcounter ("PCM Line Level Hits",""),
      hdcounter ("Hard Drive Accesses",""),
      total_latency ("Total Latency",""),
      off_chip_accesses ("Off-chip Accesses", ""),
      off_chip_SUPPLY_S_accesses ("SUPPLY_DATA_S Off-chip Accesses", ""),
      off_chip_SUPPLY_E_accesses ("SUPPLY_DATA_E Off-chip Accesses", ""),
      off_chip_SUPPLY_M_accesses ("SUPPLY_DATA_M Off-chip Accesses", ""),
      off_chip_GETIS_accesses ("GETIS Off-chip Accesses", ""),
      off_chip_GETIM_accesses ("GETIM Off-chip Accesses", ""),
      off_chip_PUTM_accesses ("PUTM Off-chip Accesses", ""),
      read_latency_hist ("Read access latency histogram", "", 350, 2, 0)
{
}

Memory_controller_stat_engine::~Memory_controller_stat_engine ()
{

}

void Memory_controller_stat_engine::global_stat_merge(Stat_engine * e)
{
    Memory_controller_stat_engine *global_engine = (Memory_controller_stat_engine*)e;
    global_engine->dmcounter += dmcounter.get_value();
    global_engine->dlcounter += dlcounter.get_value();
    global_engine->pmcounter += pmcounter.get_value();
    global_engine->plcounter += plcounter.get_value();
    global_engine->hdcounter += hdcounter.get_value();
    global_engine->total_latency += total_latency.get_value();
    global_engine->off_chip_accesses += off_chip_accesses.get_value ();
    global_engine->off_chip_SUPPLY_S_accesses += off_chip_SUPPLY_S_accesses.get_value ();
    global_engine->off_chip_SUPPLY_E_accesses += off_chip_SUPPLY_E_accesses.get_value ();
    global_engine->off_chip_SUPPLY_M_accesses += off_chip_SUPPLY_M_accesses.get_value ();
    global_engine->off_chip_GETIS_accesses += off_chip_GETIS_accesses.get_value ();
    global_engine->off_chip_GETIM_accesses += off_chip_GETIM_accesses.get_value ();
    global_engine->off_chip_PUTM_accesses += off_chip_PUTM_accesses.get_value ();
    global_engine->read_latency_hist.merge (&read_latency_hist);
}

void Memory_controller_stat_engine::print_stats (ostream & out)
{
	//if (my_module && my_module->name)
    //out << my_module->name << " OUTPUT" << endl;

    dmcounter.print(out);
    dlcounter.print(out);
    pmcounter.print(out);
    plcounter.print(out);
    hdcounter.print(out);
    total_latency.print(out);
    off_chip_accesses.print (out);
    off_chip_SUPPLY_S_accesses.print (out);
    off_chip_SUPPLY_E_accesses.print (out);
    off_chip_SUPPLY_M_accesses.print (out);
    off_chip_GETIS_accesses.print (out);
    off_chip_GETIM_accesses.print (out);
    off_chip_PUTM_accesses.print (out);
    read_latency_hist.print (out);
}

void Memory_controller_stat_engine::clear_stats()
{
    dmcounter.clear ();
    dlcounter.clear ();
    pmcounter.clear ();
    plcounter.clear ();
    hdcounter.clear ();
    total_latency.clear ();
    off_chip_accesses.clear ();
    off_chip_SUPPLY_S_accesses.clear ();
    off_chip_SUPPLY_E_accesses.clear ();
    off_chip_SUPPLY_M_accesses.clear ();
    off_chip_GETIS_accesses.clear ();
    off_chip_GETIM_accesses.clear ();
    off_chip_PUTM_accesses.clear ();
    read_latency_hist.clear ();
}

void Memory_controller_stat_engine::start_warmup ()
{
}

void Memory_controller_stat_engine::end_warmup ()
{
}

void Memory_controller_stat_engine::save_samples ()
{
}
